/***************************************************************************
 *   Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "pch.h"
#ifndef _PreComp_
# include <boost/interprocess/sync/file_lock.hpp>
# include <Inventor/errors/SoDebugError.h>
# include <Inventor/errors/SoError.h>
#include "SoFCDB.h"

# include <QtGui/QCloseEvent>
# include <QDir>
# include <QFileInfo>
# include <QLocale>
# include <QtWidgets/QMessageBox>
# include <QMessageLogContext>
# include <QtWidgets/QStatusBar>
# include <QtWidgets/QStyle>
# include <QTextStream>
# include <QTimer>
# include <QtGui/QWindow>
#endif

#include <App/Document.h>

#include "Base/Console.h"


#include "Base/FileInfo.h"

#include "Base/Stream.h"
#include <Base/Tools.h>
#include "Quarter/Quarter.h"

#include "Gui/Application.h"

#include <qcoreapplication.h>
#include <Inventor/SoDB.h>
#include <Inventor/C/tidbits.h>
#include <QtGui/QIcon>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMessageBox>

#include "AxisOrigin.h"
#include "MainWindow.h"
#include "Gui/Command.h"
#include <Gui/Document.h>




#include "View3DInventor.h"
#include "ViewProviderAnnotation.h"
#include "ViewProviderDocumentObject.h"
#include "ViewProviderDocumentObjectGroup.h"
#include "ViewProviderDragger.h"
#include "ViewProviderExtern.h"
#include "ViewProviderFeature.h"
#include "ViewProviderGeoFeatureGroup.h"
#include "ViewProviderGeometryObject.h"
#include "ViewProviderInventorObject.h"
#include "ViewProviderLine.h"

#include "ViewProviderMaterialObject.h"
#include "ViewProviderMeasureDistance.h"
#include "ViewProviderOrigin.h"
#include "ViewProviderOriginFeature.h"
#include "ViewProviderPlacement.h"
#include "ViewProviderPlane.h"
#include "ViewProviderPart.h"

#include "ViewProviderTextDocument.h"
#include "ViewProviderVRMLObject.h"

#include "Selection.h"
#include "ViewProviderExtension.h"
#include <App/Application.h>
using namespace Gui;
using namespace std;
namespace sp = std::placeholders;


Application* Application::Instance = nullptr;

namespace Gui {
    class ViewProviderMap {
        std::unordered_map<const App::DocumentObject*, ViewProvider*> map;

    public:
        void newObject(const ViewProvider& vp) {
            auto vpd = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(const_cast<ViewProvider*>(&vp));
            if (vpd && vpd->getObject())
                map[vpd->getObject()] = vpd;
        }

        void deleteObject(const ViewProvider& vp) {
            auto vpd = Base::freecad_dynamic_cast<ViewProviderDocumentObject>(const_cast<ViewProvider*>(&vp));
            if (vpd && vpd->getObject())
                map.erase(vpd->getObject());
        }

        void deleteDocument(const App::Document& doc) {
            for (auto obj : doc.getObjects())
                map.erase(obj);
        }

        Gui::ViewProvider* getViewProvider(const App::DocumentObject* obj) const {
            auto it = map.find(obj);
            if (it == map.end())
                return nullptr;
            return it->second;
        }
    };

    // Pimpl class
    struct ApplicationP {
        ApplicationP(bool GUIenabled) :
            activeDocument(nullptr),
            editDocument(nullptr),
            isClosing(false),
            startingUp(true) {
            // create the macro manager

            // Create the Theme Manager
        }

        ~ApplicationP() {
            
        }

        /// list of all handled documents
        std::map<const App::Document*, Gui::Document*> documents;
        /// Active document
        Gui::Document* activeDocument;
        Gui::Document* editDocument;
        PreferencePackManager* prefPackManager;
        /// List of all registered views
        std::list<Gui::BaseView*> passive;
        bool isClosing;
        bool startingUp;
        /// Handles all commands
        CommandManager commandManager;
        ViewProviderMap viewproviderMap;
    };
} // namespace Gui

Application::Application(bool GUIenabled) {
    //App::GetApplication().Attach(this);
    if (GUIenabled) {
        App::GetApplication().signalNewDocument.connect(std::bind(&Gui::Application::slotNewDocument, this, sp::_1, sp::_2));
        App::GetApplication().signalDeleteDocument.connect(std::bind(&Gui::Application::slotDeleteDocument, this, sp::_1));
        App::GetApplication().signalRenameDocument.connect(std::bind(&Gui::Application::slotRenameDocument, this, sp::_1));
        App::GetApplication().signalActiveDocument.connect(std::bind(&Gui::Application::slotActiveDocument, this, sp::_1));
        App::GetApplication().signalRelabelDocument.connect(std::bind(&Gui::Application::slotRelabelDocument, this, sp::_1));
        App::GetApplication().signalShowHidden.connect(std::bind(&Gui::Application::slotShowHidden, this, sp::_1));


        // install the last active language


        // Coin3d disabled VBO support for all Intel drivers but in the meantime they have improved
        // so we can try to override the workaround by setting COIN_VBO
        if (false) {
            (void)coin_setenv("COIN_VBO", "0", true);
        }

        // Check for the symbols for group separator and decimal point. They must be different otherwise
        // Qt doesn't work properly.
#if defined(Q_OS_WIN32)
        if (QLocale().groupSeparator() == QLocale().decimalPoint()) {
            QMessageBox::critical(0, QLatin1String("Invalid system settings"),
                                  QLatin1String("Your system uses the same symbol for decimal point and group separator.\n\n"
                                      "This causes serious problems and makes the application fail to work properly.\n"
                                      "Go to the system configuration panel of the OS and fix this issue, please."));
            throw std::exception("Invalid system settings");
        }
#endif
#if 0 // QuantitySpinBox and InputField try to handle the group separator now
        // http://forum.freecadweb.org/viewtopic.php?f=10&t=6910
        // A workaround is to disable the group separator for double-to-string conversion, i.e.
        // setting the flag 'OmitGroupSeparator'.
        QLocale loc;
        loc.setNumberOptions(QLocale::OmitGroupSeparator);
        QLocale::setDefault(loc);
#endif
    }


    App::Application::Config()["COIN_VERSION"] = COIN_VERSION;

    d = new ApplicationP(GUIenabled);

    // global access
    Instance = this;


    // if (GUIenabled) {
    if (false) {
        createStandardOperations();
    }
}

Application::~Application() {
    Base::Console().Log("Destruct Gui::Application\n");
    SelectionSingleton::destruct();
    delete d;
    Instance = nullptr;
}


//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// creating std commands
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void Application::open(const char* FileName, const char* Module) {}

void Application::importFrom(const char* FileName, const char* DocName, const char* Module) {}

void Application::exportTo(const char* FileName, const char* DocName, const char* Module) {}

void Application::createStandardOperations() {
    // register the application Standard commands from CommandStd.cpp
    /*Gui::CreateStdCommands();
    Gui::CreateDocCommands();
    Gui::CreateFeatCommands();
    Gui::CreateMacroCommands();
    Gui::CreateViewStdCommands();
    Gui::CreateWindowStdCommands();
    Gui::CreateStructureCommands();
    Gui::CreateTestCommands();
    Gui::CreateLinkCommands();*/
}

void Application::slotNewDocument(const App::Document& Doc, bool isMainDoc) {
#ifdef FC_DEBUG
    std::map<const App::Document*, Gui::Document*>::const_iterator it = d->documents.find(&Doc);
    assert(it==d->documents.end());
#endif
    Gui::Document* pDoc = new Gui::Document(const_cast<App::Document*>(&Doc),this);
    d->documents[&Doc] = pDoc;

    // connect the signals to the application for the new document
    pDoc->signalNewObject.connect(std::bind(&Gui::Application::slotNewObject, this, sp::_1));
    pDoc->signalDeletedObject.connect(std::bind(&Gui::Application::slotDeletedObject, this, sp::_1));
    pDoc->signalChangedObject.connect(std::bind(&Gui::Application::slotChangedObject, this, sp::_1, sp::_2));
    pDoc->signalRelabelObject.connect(std::bind(&Gui::Application::slotRelabelObject, this, sp::_1));
    pDoc->signalActivatedObject.connect(std::bind(&Gui::Application::slotActivatedObject, this, sp::_1));
    pDoc->signalInEdit.connect(std::bind(&Gui::Application::slotInEdit, this, sp::_1));
    pDoc->signalResetEdit.connect(std::bind(&Gui::Application::slotResetEdit, this, sp::_1));

    signalNewDocument(*pDoc, isMainDoc);
    if (isMainDoc) {
        pDoc->createView(View3DInventor::getClassTypeId());
    }
    d->activeDocument = pDoc;
    d->editDocument = pDoc;
}

void Application::slotDeleteDocument(const App::Document& Doc) {}

void Application::slotRelabelDocument(const App::Document& Doc) {}

void Application::slotRenameDocument(const App::Document& Doc) {
    std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
#ifdef FC_DEBUG
    assert(doc!=d->documents.end());
#endif

    signalRenameDocument(*doc->second);
}

void Application::slotShowHidden(const App::Document& Doc) {
    std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
#ifdef FC_DEBUG
    assert(doc!=d->documents.end());
#endif

    signalShowHidden(*doc->second);
}

void Application::slotActiveDocument(const App::Document& Doc) {}

void Application::slotNewObject(const ViewProvider& vp) {
    d->viewproviderMap.newObject(vp);
    this->signalNewObject(vp);
}

void Application::slotDeletedObject(const ViewProvider& vp) {
    this->signalDeletedObject(vp);
    d->viewproviderMap.deleteObject(vp);
}

void Application::slotChangedObject(const ViewProvider& vp, const App::Property& prop)
{
    this->signalChangedObject(vp,prop);
    updateActions(true);
}

void Application::slotRelabelObject(const ViewProvider& vp) {
    this->signalRelabelObject(vp);
}

void Application::slotActivatedObject(const ViewProvider& vp) {
    this->signalActivatedObject(vp);
    updateActions();
}

void Application::slotInEdit(const Gui::ViewProviderDocumentObject& vp) {
    this->signalInEdit(vp);
}

void Application::slotResetEdit(const Gui::ViewProviderDocumentObject& vp) {
    this->signalResetEdit(vp);
}

void Application::onLastWindowClosed(Gui::Document* pcDoc) {}

/// send Messages to the active view
bool Application::sendMsgToActiveView(const char* pMsg, const char** ppReturn) {
    // MDIView* pView = getMainWindow()->activeWindow();
    MDIView* pView = nullptr;
    bool res = pView ? pView->onMsg(pMsg, ppReturn) : false;
    updateActions(true);
    return res;
}

bool Application::sendHasMsgToActiveView(const char* pMsg) {
    // MDIView* pView = getMainWindow()->activeWindow();
    MDIView* pView = nullptr;
    return pView ? pView->onHasMsg(pMsg) : false;
}

/// send Messages to the active view
bool Application::sendMsgToFocusView(const char* pMsg, const char** ppReturn) {return 0;}

bool Application::sendHasMsgToFocusView(const char* pMsg) {return 0;}

Gui::MDIView* Application::activeView() const {return nullptr;}

/**
 * @brief Application::activateView
 * Activates a view of the given type of the active document.
 * If a view of this type doesn't exist and \a create is true
 * a new view of this type will be created.
 * @param type
 * @param create
 */
void Application::activateView(const Base::Type& type, bool create) {}

/// Getter for the active view
Gui::Document* Application::activeDocument() const {
    return d->activeDocument;
}

Gui::Document* Application::editDocument() const {
    return d->editDocument;
}

Gui::MDIView* Application::editViewOfNode(SoNode* node) const {return nullptr;}

void Application::setEditDocument(Gui::Document* doc) {}

void Application::setActiveDocument(Gui::Document* pcDocument) {}

Gui::Document* Application::getDocument(const char* name) const {
    App::Document* pDoc = App::GetApplication().getDocument(name);
    std::map<const App::Document*, Gui::Document*>::const_iterator it = d->documents.find(pDoc);
    if (it != d->documents.end())
        return it->second;
    else
        return nullptr;
}

Gui::Document* Application::getDocument(const App::Document* pDoc) const {
    std::map<const App::Document*, Gui::Document*>::const_iterator it = d->documents.find(pDoc);
    if (it != d->documents.end())
        return it->second;
    else
        return nullptr;
}

void Application::showViewProvider(const App::DocumentObject* obj) {
    ViewProvider* vp = getViewProvider(obj);
    if (vp) vp->show();
}

void Application::hideViewProvider(const App::DocumentObject* obj) {
    ViewProvider* vp = getViewProvider(obj);
    if (vp) vp->hide();
}

Gui::ViewProvider* Application::getViewProvider(const App::DocumentObject* obj) const {
    return d->viewproviderMap.getViewProvider(obj);
}

void Application::attachView(Gui::BaseView* pcView) {
    d->passive.push_back(pcView);
}

void Application::detachView(Gui::BaseView* pcView) {
    d->passive.remove(pcView);
}

void Application::onUpdate() {
    // update all documents
    std::map<const App::Document*, Gui::Document*>::iterator It;
    for (It = d->documents.begin(); It != d->documents.end(); ++It)
        It->second->onUpdate();
    // update all the independent views
    /*for (std::list<Gui::BaseView*>::iterator It2 = d->passive.begin();It2 != d->passive.end();++It2) {
        (*It2)->onUpdate();
    }*/
}

/// Gets called if a view gets activated, this manages the whole activation scheme
void Application::viewActivated(MDIView* pcView) {
#ifdef FC_DEBUG
    // May be useful for error detection
    Base::Console().Log("Active view is %s (at %p)\n",
                 (const char*)pcView->windowTitle().toUtf8(),pcView);
#endif

    signalActivateView(pcView);

    // Set the new active document which is taken of the activated view. If, however,
    // this view is passive we let the currently active document unchanged as we would
    // have no document active which is causing a lot of trouble.
    if (!pcView->isPassive())
        setActiveDocument(pcView->getGuiDocument());
}


void Application::updateActive() {
    activeDocument()->onUpdate();
}

void Application::updateActions(bool delay) {
    // getMainWindow()->updateActions(delay);
}

void Application::tryClose(QCloseEvent* e) {}

int Application::getUserEditMode(const std::string& mode) const {
    if (mode.empty()) {
        return userEditMode;
    }
    for (auto const& uem : userEditModes) {
        if (uem.second == mode) {
            return uem.first;
        }
    }
    return -1;
}

std::string Application::getUserEditModeName(int mode) const {
    if (mode == -1) {
        return userEditModes.at(userEditMode);
    }
    if (userEditModes.find(mode) != userEditModes.end()) {
        return userEditModes.at(mode);
    }
    return "";
}

bool Application::setUserEditMode(int mode) {
    if (userEditModes.find(mode) != userEditModes.end() && userEditMode != mode) {
        userEditMode = mode;
        this->signalUserEditModeChanged(userEditMode);
        return true;
    }
    return false;
}

bool Application::setUserEditMode(const std::string& mode) {
    for (auto const& uem : userEditModes) {
        if (uem.second == mode) {
            return setUserEditMode(uem.first);
        }
    }
    return false;
}

/**
 * Activate the matching workbench to the registered workbench handler with name \a name.
 * The handler must be an instance of a class written in Python.
 * Normally, if a handler gets activated a workbench with the same name gets created unless it
 * already exists.
 *
 * The old workbench gets deactivated before. If the workbench to the handler is already
 * active or if the switch fails false is returned.
 */
bool Application::activateWorkbench(const char* name) { return 0; }

QPixmap Application::workbenchIcon(const QString& wb) const {
    return QPixmap();
}

QString Application::workbenchToolTip(const QString& wb) const {
    return QString();
}

QString Application::workbenchMenuText(const QString& wb) const {
    return QString();
}

QStringList Application::workbenches() const {
    QStringList wb;
    return wb;
}

void Application::setupContextMenu(const char* recipient, MenuItem* items) const {}

bool Application::isClosing() {
    return d->isClosing;
}


CommandManager& Application::commandManager() {
    return d->commandManager;
}

Gui::PreferencePackManager* Application::prefPackManager() {
    return d->prefPackManager;
}
void Application::set_action(int id) {
    switch (id) {
    case 45050:
        // create_line
        Gui::Application::Instance->commandManager().getCommandByName("Sketcher_CreateLine")->invoke(0, Gui::Command::TriggerAction);
        Base::Console().Log("create_line");
        break;
    default:
        Base::Console().Warning("Unknown action ID: %d\n", id);
        break;
    }
}
void Application::new_sketch() {
    Gui::Application::Instance->commandManager().getCommandByName("Sketcher_NewSketch")->invoke(0, Gui::Command::TriggerAction);
}

//**************************************************************************
// Init, Destruct and singleton

typedef void (*_qt_msg_handler_old)(QtMsgType, const QMessageLogContext&, const QString&);
_qt_msg_handler_old old_qtmsg_handler = nullptr;

void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
    Q_UNUSED(context);
#ifdef FC_DEBUG
    switch (type)
    {
    case QtInfoMsg:
    case QtDebugMsg:
        Base::Console().Message("%s\n", msg.toUtf8().constData());
        break;
    case QtWarningMsg:
        Base::Console().Warning("%s\n", msg.toUtf8().constData());
        break;
    case QtCriticalMsg:
        Base::Console().Error("%s\n", msg.toUtf8().constData());
        break;
    case QtFatalMsg:
        Base::Console().Error("%s\n", msg.toUtf8().constData());
        abort();                    // deliberately core dump
    }
#ifdef FC_OS_WIN32
    if (old_qtmsg_handler)
        (*old_qtmsg_handler)(type, context, msg);
#endif
#else
    // do not stress user with Qt internals but write to log file if enabled
    Q_UNUSED(type);
    Base::Console().Log("%s\n", msg.toUtf8().constData());
#endif
}

#ifdef FC_DEBUG // redirect Coin messages to FreeCAD
void messageHandlerCoin(const SoError * error, void * /*userdata*/)
{
    if (error && error->getTypeId() == SoDebugError::getClassTypeId()) {
        const SoDebugError* dbg = static_cast<const SoDebugError*>(error);
        const char* msg = error->getDebugString().getString();
        switch (dbg->getSeverity())
        {
        case SoDebugError::INFO:
            Base::Console().Message("%s\n", msg);
            break;
        case SoDebugError::WARNING:
            Base::Console().Warning("%s\n", msg);
            break;
        default: // error
            Base::Console().Error("%s\n", msg);
            break;
        }
#ifdef FC_OS_WIN32
    if (old_qtmsg_handler)
        (*old_qtmsg_handler)(QtDebugMsg, QMessageLogContext(), QString::fromLatin1(msg));
#endif
    }
    else if (error) {
        const char* msg = error->getDebugString().getString();
        Base::Console().Log( msg );
    }
}

#endif

// To fix bug #0000345 move Q_INIT_RESOURCE() outside initApplication()
static void init_resources() {
    // init resources
    // Q_INIT_RESOURCE(resource);
    // Q_INIT_RESOURCE(translation);
}

void Application::initApplication() {
    static bool init = false;
    if (init) {
        Base::Console().Error("Tried to run Gui::Application::initApplication() twice!\n");
        return;
    }

    try {
        initTypes();
        init_resources();
        old_qtmsg_handler = qInstallMessageHandler(messageHandler);
        init = true;
    } catch (...) {
        // force to flush the log
        App::Application::destructObserver();
        throw;
    }
}

void Application::initTypes() {
    // views
    Gui::BaseView                               ::init();
    Gui::MDIView::init();
    Gui::View3DInventor::init();
    // View Provider
    Gui::ViewProvider::init();
    Gui::ViewProviderExtension                  ::init();
    /*Gui::ViewProviderExtensionPython            ::init();
    Gui::ViewProviderGroupExtension             ::init();
    Gui::ViewProviderGroupExtensionPython       ::init();
    Gui::ViewProviderGeoFeatureGroupExtension   ::init();
    Gui::ViewProviderGeoFeatureGroupExtensionPython::init();
    Gui::ViewProviderOriginGroupExtension       ::init();
    Gui::ViewProviderOriginGroupExtensionPython ::init();*/
    Gui::ViewProviderExtern::init();
    Gui::ViewProviderDocumentObject::init();
    Gui::ViewProviderFeature::init();
    Gui::ViewProviderDocumentObjectGroup::init();
    Gui::ViewProviderDragger::init();
    Gui::ViewProviderGeometryObject::init();
    Gui::ViewProviderInventorObject::init();
    Gui::ViewProviderVRMLObject::init();
    Gui::ViewProviderAnnotation::init();
    Gui::ViewProviderAnnotationLabel::init();
    Gui::ViewProviderPointMarker::init();
    Gui::ViewProviderMeasureDistance::init();
    Gui::ViewProviderPlacement::init();
    Gui::ViewProviderOriginFeature::init();
    Gui::ViewProviderPlane::init();
    Gui::ViewProviderLine::init();
    Gui::ViewProviderGeoFeatureGroup::init();
    Gui::ViewProviderPart::init();
    Gui::ViewProviderOrigin::init();
    Gui::ViewProviderMaterialObject::init();
    Gui::ViewProviderTextDocument::init();
    Gui::AxisOrigin::init();
}

void Application::initOpenInventor() {
    // init the Inventor subsystem
    SoDB::init();
    SIM::Coin3D::Quarter::Quarter::init();
    SoFCDB::init();
}

void Application::runInitGuiScript() {}

void import_sketch();

void import_sketch_gui();

void Application::runApplication()
{
    const std::map<std::string,std::string>& cfg = App::Application::Config();
    std::map<std::string,std::string>::const_iterator it;

    QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);

#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
    QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
#endif

    // Automatic scaling for legacy apps (disable once all parts of GUI are aware of HiDpi)
    ParameterGrp::handle hDPI = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/HighDPI");
    bool disableDpiScaling = hDPI->GetBool("DisableDpiScaling", false);
    if (disableDpiScaling) {
#ifdef FC_OS_WIN32
        SetProcessDPIAware(); // call before the main event loop
#endif
        QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
    }
    else {
        // Enable automatic scaling based on pixel density of display (added in Qt 5.6)
        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#if QT_VERSION >= QT_VERSION_CHECK(5,14,0)
        QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
    }

    //Enable support for highres images (added in Qt 5.1, but off by default)
    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);

    // Use software rendering for OpenGL
    ParameterGrp::handle hOpenGL = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/OpenGL");
    bool useSoftwareOpenGL = hOpenGL->GetBool("UseSoftwareOpenGL", false);
    if (useSoftwareOpenGL) {
        QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
    }

    #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
        // By default (on platforms that support it, see docs for
        // Qt::AA_CompressHighFrequencyEvents) QT applies compression
        // for high frequency events (mouse move, touch, window resizes)
        // to keep things smooth even when handling the event takes a
        // while (e.g. to calculate snapping).
        // However, tablet pen move events (and mouse move events
        // synthesised from those) are not compressed by default (to
        // allow maximum precision when e.g. hand-drawing curves),
        // leading to unacceptable slowdowns using a tablet pen. Enable
        // compression for tablet events here to solve that.
        QCoreApplication::setAttribute(Qt::AA_CompressTabletEvents);
    #endif

    // A new QApplication
    Base::Console().Log("Init: Creating Gui::Application and QApplication\n");

    // if application not yet created by the splasher
    int argc = App::Application::GetARGC();
    char** argv = App::Application::GetARGV();
    QApplication mainApp(argc, argv);
    // http://forum.freecadweb.org/viewtopic.php?f=3&t=15540
    mainApp.setAttribute(Qt::AA_DontShowIconsInMenus, false);

    // Make sure that we use '.' as decimal point. See also
    // http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=559846
    // and issue #0002891
    // http://doc.qt.io/qt-5/qcoreapplication.html#locale-settings
    setlocale(LC_NUMERIC, "C");

    // check if a single or multiple instances can run
    it = cfg.find("SingleInstance");
    if (it != cfg.end()){ // && mainApp.isRunning()) {
        // send the file names to be opened to the server application so that this
        // opens them
        QDir cwd = QDir::current();
        std::list<std::string> files = App::Application::getCmdLineFiles();
        for (std::list<std::string>::iterator jt = files.begin(); jt != files.end(); ++jt) {
            QString fn = QString::fromUtf8(jt->c_str(), static_cast<int>(jt->size()));
            QFileInfo fi(fn);
            // if path name is relative make it absolute because the running instance
            // cannot determine the full path when trying to load the file
            if (fi.isRelative()) {
                fn = cwd.absoluteFilePath(fn);
                fn = QDir::cleanPath(fn);
            }

            QByteArray msg = fn.toUtf8();
            msg.prepend("OpenFile:");
            /*if (!mainApp.sendMessage(msg)) {
                qWarning("Failed to send message to server");
                break;
            }*/
        }
        return;
    }

    // set application icon and window title
    it = cfg.find("Application");
    if (it != cfg.end()) {
        mainApp.setApplicationName(QString::fromUtf8(it->second.c_str()));
    }
    else {
        mainApp.setApplicationName(QString::fromStdString(App::Application::getExecutableName()));
    }
#ifndef Q_OS_MACX
    // mainApp.setWindowIcon(Gui::BitmapFactory().pixmap(App::Application::Config()["AppIcon"].c_str()));
#endif
    QString plugin;
    plugin = QString::fromStdString(App::Application::getHomePath());
    plugin += QLatin1String("/plugins");
    QCoreApplication::addLibraryPath(plugin);

    // setup the search paths for Qt style sheets
    QStringList qssPaths;
    qssPaths << QString::fromUtf8((App::Application::getUserAppDataDir() + "Gui/Stylesheets/").c_str())
             << QString::fromUtf8((App::Application::getResourceDir() + "Gui/Stylesheets/").c_str())
             << QLatin1String(":/stylesheets");
    QDir::setSearchPaths(QString::fromLatin1("qss"), qssPaths);

    // set search paths for images
    QStringList imagePaths;
    imagePaths << QString::fromUtf8((App::Application::getUserAppDataDir() + "Gui/images").c_str())
               << QString::fromUtf8((App::Application::getUserAppDataDir() + "pixmaps").c_str())
               << QLatin1String(":/icons");
    QDir::setSearchPaths(QString::fromLatin1("images"), imagePaths);

    // register action style event type
    ActionStyleEvent::EventType = QEvent::registerEventType(QEvent::User + 1);

    ParameterGrp::handle hTheme = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Bitmaps/Theme");
#if !defined(Q_OS_LINUX)
    QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() << QString::fromLatin1(":/icons/FreeCAD-default"));
    QIcon::setThemeName(QLatin1String("FreeCAD-default"));
#else
    // Option to opt-out from using a Linux desktop icon theme.
    // https://forum.freecadweb.org/viewtopic.php?f=4&t=35624
    bool themePaths = hTheme->GetBool("ThemeSearchPaths",true);
    if (!themePaths) {
        QStringList searchPaths;
        searchPaths.prepend(QString::fromUtf8(":/icons"));
        QIcon::setThemeSearchPaths(searchPaths);
        QIcon::setThemeName(QLatin1String("FreeCAD-default"));
    }
#endif

    std::string searchpath = hTheme->GetASCII("SearchPath");
    if (!searchpath.empty()) {
        QStringList searchPaths = QIcon::themeSearchPaths();
        searchPaths.prepend(QString::fromUtf8(searchpath.c_str()));
        QIcon::setThemeSearchPaths(searchPaths);
    }

    std::string name = hTheme->GetASCII("Name");
    if (!name.empty()) {
        QIcon::setThemeName(QString::fromLatin1(name.c_str()));
    }

#if defined(FC_OS_LINUX)
    // See #0001588
    QString path = FileDialog::restoreLocation();
    FileDialog::setWorkingDirectory(QDir::currentPath());
    FileDialog::saveLocation(path);
#else
    // FileDialog::setWorkingDirectory(FileDialog::restoreLocation());
#endif

    Application app(true);
    MainWindow mw;
    mw.setProperty("QuitOnClosed", true);

    // allow to disable version number
    ParameterGrp::handle hGen = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General");
    bool showVersion = hGen->GetBool("ShowVersionInTitle",false);

    if (showVersion) {
        // set main window title with FreeCAD Version
        std::map<std::string, std::string>& config = App::Application::Config();
        QString major  = QString::fromLatin1(config["BuildVersionMajor"].c_str());
        QString minor  = QString::fromLatin1(config["BuildVersionMinor"].c_str());
        QString point = QString::fromLatin1(config["BuildVersionPoint"].c_str());
        QString title = QString::fromLatin1("%1 %2.%3.%4").arg(mainApp.applicationName(), major, minor, point);
        mw.setWindowTitle(title);
    } else {
        mw.setWindowTitle(mainApp.applicationName());
    }

    QObject::connect(&mainApp, SIGNAL(messageReceived(const QList<QByteArray> &)),
                     &mw, SLOT(processMessages(const QList<QByteArray> &)));

    /*ParameterGrp::handle hDocGrp = WindowParameter::getDefaultParameter()->GetGroup("Document");
    int timeout = hDocGrp->GetInt("AutoSaveTimeout", 15); // 15 min
    if (!hDocGrp->GetBool("AutoSaveEnabled", true))
        timeout = 0;
    AutoSaver::instance()->setTimeout(timeout * 60000);
    AutoSaver::instance()->setCompressed(hDocGrp->GetBool("AutoSaveCompressed", true));*/

    // set toolbar icon size
    /*ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
    int size = hGrp->GetInt("ToolbarIconSize", 0);
    if (size >= 16) // must not be lower than this
        mw.setIconSize(QSize(size,size));

    // filter wheel events for combo boxes
    if (hGrp->GetBool("ComboBoxWheelEventFilter", false)) {
        WheelEventFilter* filter = new WheelEventFilter(&mainApp);
        mainApp.installEventFilter(filter);
    }
    
    //filter keyboard events to substitute decimal separator
    if (hGrp->GetBool("SubstituteDecimalSeparator", false)) {
        KeyboardFilter* filter = new KeyboardFilter(&mainApp);
        mainApp.installEventFilter(filter);
    }

    // For values different to 1 and 2 use the OS locale settings
    auto localeFormat = hGrp->GetInt("UseLocaleFormatting", 0);
    if (localeFormat == 1) {
        Translator::instance()->setLocale(hGrp->GetASCII("Language", Translator::instance()->activeLanguage().c_str()));
    }
    else if (localeFormat == 2) {
        Translator::instance()->setLocale("C");
    }*/

    // set text cursor blinking state
    int blinkTime = -1;// hGrp->GetBool("EnableCursorBlinking", true) ? -1 : 0;
    qApp->setCursorFlashTime(blinkTime);

    {
        QWindow window;
        window.setSurfaceType(QWindow::OpenGLSurface);
        window.create();

        QOpenGLContext context;
        if (context.create()) {
            context.makeCurrent(&window);
            if (!context.functions()->hasOpenGLFeature(QOpenGLFunctions::Framebuffers)) {
                Base::Console().Log("This system does not support framebuffer objects\n");
            }
            if (!context.functions()->hasOpenGLFeature(QOpenGLFunctions::NPOTTextures)) {
                Base::Console().Log("This system does not support NPOT textures\n");
            }

            int major = context.format().majorVersion();
            int minor = context.format().minorVersion();
            const char* glVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
            Base::Console().Log("OpenGL version is: %d.%d (%s)\n", major, minor, glVersion);
        }
    }

    // init the Inventor subsystem
    initOpenInventor();

    QString home = QString::fromStdString(App::Application::getHomePath());

    it = cfg.find("WindowTitle");
    if (it != cfg.end()) {
        QString title = QString::fromUtf8(it->second.c_str());
        mw.setWindowTitle(title);
    }
    it = cfg.find("WindowIcon");
    if (it != cfg.end()) {
        QString path = QString::fromUtf8(it->second.c_str());
        if (QDir(path).isRelative()) {
            path = QFileInfo(QDir(home), path).absoluteFilePath();
        }
        QApplication::setWindowIcon(QIcon(path));
    }
    it = cfg.find("ProgramLogo");
    if (it != cfg.end()) {
        QString path = QString::fromUtf8(it->second.c_str());
        if (QDir(path).isRelative()) {
            path = QFileInfo(QDir(home), path).absoluteFilePath();
        }
        QPixmap px(path);
        if (!px.isNull()) {
            QLabel* logo = new QLabel();
            logo->setPixmap(px.scaledToHeight(32));
            mw.statusBar()->addPermanentWidget(logo, 0);
            logo->setFrameShape(QFrame::NoFrame);
        }
    }
    bool hidden = false;
    it = cfg.find("StartHidden");
    if (it != cfg.end()) {
        hidden = true;
    }

    // show splasher while initializing the GUI
    if (!hidden)
        mw.startSplasher();

    // running the GUI init script
    try {
        Base::Console().Log("Run Gui init script\n");
        runInitGuiScript();
    }
    catch (const Base::Exception& e) {
        Base::Console().Error("Error in FreeCADGuiInit.py: %s\n", e.what());
        mw.stopSplasher();
        throw;
    }

    // stop splash screen and set immediately the active window that may be of interest
    // for scripts using Python binding for Qt
    mw.stopSplasher();
    mainApp.setActiveWindow(&mw);

    // Activate the correct workbench
    std::string start = App::Application::Config()["StartWorkbench"];
    Base::Console().Log("Init: Activating default workbench %s\n", start.c_str());
    std::string autoload = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")->
                           GetASCII("AutoloadModule", start.c_str());
    if ("$LastModule" == autoload) {
        start = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")->
                               GetASCII("LastModule", start.c_str());
    } else {
        start = autoload;
    }
    // if the auto workbench is not visible then force to use the default workbech
    // and replace the wrong entry in the parameters
    QStringList wb = app.workbenches();
    if (!wb.contains(QString::fromLatin1(start.c_str()))) {
        start = App::Application::Config()["StartWorkbench"];
        if ("$LastModule" == autoload) {
            App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")->
                                  SetASCII("LastModule", start.c_str());
        } else {
            App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")->
                                  SetASCII("AutoloadModule", start.c_str());
        }
    }

    // Call this before showing the main window because otherwise:
    // 1. it shows a white window for a few seconds which doesn't look nice
    // 2. the layout of the toolbars is completely broken
    app.activateWorkbench(start.c_str());

    // show the main window
    if (!hidden) {
        Base::Console().Log("Init: Showing main window\n");
        mw.loadWindowSettings();
    }

    /*hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow");
    std::string style = hGrp->GetASCII("StyleSheet");
    if (style.empty()) {
        // check the branding settings
        const auto& config = App::Application::Config();
        auto it = config.find("StyleSheet");
        if (it != config.end())
            style = it->second;
    }*/

    // app.setStyleSheet(QLatin1String(style.c_str()), hGrp->GetBool("TiledBackground", false));

    //initialize spaceball.
    // mainApp.initSpaceball(&mw);

#ifdef FC_DEBUG // redirect Coin messages to FreeCAD
    SoDebugError::setHandlerCallback( messageHandlerCoin, 0 );
#endif

    // Now run the background autoload, for workbenches that should be loaded at startup, but not
    // displayed to the user immediately
    std::string autoloadCSV = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")->
        GetASCII("BackgroundAutoloadModules", "");
    // BackgroundAutoloadModules
    import_sketch();
    import_sketch_gui();
    // Tokenize the comma-separated list and load the requested workbenches if they exist in this installation
    std::vector<std::string> backgroundAutoloadedModules;
    std::stringstream stream(autoloadCSV);
    std::string workbench;
    while (std::getline(stream, workbench, ',')) {
        if (wb.contains(QString::fromLatin1(workbench.c_str())))
            app.activateWorkbench(workbench.c_str());
    }

    // Reactivate the startup workbench
    app.activateWorkbench(start.c_str());

    Instance->d->startingUp = false;

    // gets called once we start the event loop
    QTimer::singleShot(0, &mw, SLOT(delayedStartup()));

    // run the Application event loop
    Base::Console().Log("Init: Entering event loop\n");
    App::GetApplication().newDocument();
    static_cast<Gui::View3DInventor *>(Gui::Application::Instance->activeDocument()->getActiveView())->viewDefaultOrientation();
    Gui::Application::Instance->new_sketch();
    // boot phase reference point
    // https://forum.freecadweb.org/viewtopic.php?f=10&t=21665
    Gui::getMainWindow()->setProperty("eventLoop", true);

    try {
        std::stringstream s;
        s << App::Application::getUserCachePath() << App::Application::getExecutableName()
          << "_" << QCoreApplication::applicationPid() << ".lock";
        // open a lock file with the PID
        Base::FileInfo fi(s.str());
        Base::ofstream lock(fi);

        // In case the file_lock cannot be created start FreeCAD without IPC support.
#if !defined(FC_OS_WIN32) || (BOOST_VERSION < 107600)
        std::string filename = s.str();
#else
        std::wstring filename = fi.toStdWString();
#endif
        std::unique_ptr<boost::interprocess::file_lock> flock;
        try {
            flock = std::make_unique<boost::interprocess::file_lock>(filename.c_str());
            flock->lock();
        }
        catch (const boost::interprocess::interprocess_exception& e) {
            QString msg = QString::fromLocal8Bit(e.what());
            Base::Console().Warning("Failed to create a file lock for the IPC: %s\n", msg.toUtf8().constData());
        }

        Base::Console().Log("Init: Executing event loop...\n");
        mainApp.exec();

        // Qt can't handle exceptions thrown from event handlers, so we need
        // to manually rethrow SystemExitExceptions.
        /*if (mainApp.caughtException.get())
            throw Base::SystemExitException(*mainApp.caughtException.get());*/

        // close the lock file, in case of a crash we can see the existing lock file
        // on the next restart and try to repair the documents, if needed.
        if (flock.get())
            flock->unlock();
        lock.close();
        fi.deleteFile();
    }
    /*catch (const Base::SystemExitException&) {
        Base::Console().Message("System exit\n");
        throw;
    }*/
    catch (const std::exception& e) {
        // catching nasty stuff coming out of the event loop
        Base::Console().Error("Event loop left through unhandled exception: %s\n", e.what());
        App::Application::destructObserver();
        throw;
    }
    catch (...) {
        // catching nasty stuff coming out of the event loop
        Base::Console().Error("Event loop left through unknown unhandled exception\n");
        App::Application::destructObserver();
        throw;
    }

    Base::Console().Log("Finish: Event loop left\n");
}


void Application::setStyleSheet(const QString& qssFile, bool tiledBackground) {
    // Qt's style sheet doesn't support it to define the link color of a QLabel
    // or in the property editor when an expression is set because therefore the
    // link color of the application's palette is used.
    // A workaround is to set a user-defined property to e.g. a QLabel and then
    // define it in the .qss file.
    //
    // Example:
    // QLabel label;
    // label.setProperty("haslink", QByteArray("true"));
    // label.show();
    // QColor link = label.palette().color(QPalette::Text);
    //
    // The .qss file must define it with:
    // QLabel[haslink="true"] {
    //     color: #rrggbb;
    // }
    //
    // See https://stackoverflow.com/questions/5497799/how-do-i-customise-the-appearance-of-links-in-qlabels-using-style-sheets
    // and https://forum.freecadweb.org/viewtopic.php?f=34&t=50744

    // At startup time unpolish() mustn't be executed because otherwise the QSint widget
    // appear incorrect due to an outdated cache.
    // See https://doc.qt.io/qt-5/qstyle.html#unpolish-1
    // See https://forum.freecadweb.org/viewtopic.php?f=17&t=50783
}

void Application::checkForPreviousCrashes() {
    // If the recovery dialog wasn't shown check the cache size periodically
}

App::Document* Application::reopen(App::Document* doc) {
    if (!doc)
        return nullptr;
    return doc;
}

void Application::set_widgets(QWidget* qw) {
    main_widget =qw;
}
